package com.jbob.mysql;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.HashMap;

import com.jbob.collection.Balise;
import com.jbob.collection.DocumentXml;
import com.jbob.collection.Query;
import com.jbob.collection.Terme;
import com.jbob.constants.CONSTANTS;
import com.jbob.parser.ParserHelper;
import com.mysql.jdbc.ResultSet;
import com.mysql.jdbc.Statement;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

/**
 * Classe en charge de g�rer les interractions avec la BD MySQL
 * @author lucas.bouabdelli jean-baptiste.guy
 */
public class MySQLHelper
{
	private static HashMap<String, Integer> listTermsInserted = new HashMap<>();
	private static Connection connection = null;
	
	public MySQLHelper() 
	{
	}
	
	public static void indexationBdd() {
		
		// On vide les tables
		initialiseBBd();
		
		// Insertion de tous les termes
		insertAllTerms();
		
		// R�cup�ration de tous les termes pour avoir leurs id
		selectAllTerms();
		
		Connection con = getConnection();
		
		long termeBaliseInsert = 0;
		// **************************************************
		// Cr�ation des StringBuilder permettant de r�aliser les requetes d'INSERT de terme_balise et balise
		// Cette m�thode permet de ne lancer qu'une seule requete => gain cons�quent de performance
		// **
		// BALISE
		StringBuilder queryInsertBalise = new StringBuilder("INSERT INTO balise (id, nom, path, document_id) VALUES ");
		boolean bFirstBalise = true;
		
		int baliseId = 1;
		// TERME_BALISE
		StringBuilder queryInsertTermeBalise = new StringBuilder("INSERT INTO terme_balise (balise_id, terme_id, liste_position, frequence) VALUES ");
		boolean bFirstTermeBalise = true;
		// **************************************************
		System.out.println(" -- D�but de l'insertion en base -- ");
		for (DocumentXml document : ParserHelper.getDocumentsList()) {
			// *********************
			// Insertion des documents
			System.out.println("\t" + document.getName());
			try {
				String queryInsertDocument = "INSERT INTO document (nom) VALUES (?);";
				PreparedStatement preparedStmtDocument = con.prepareStatement(queryInsertDocument);
				preparedStmtDocument.setString (1, document.getName());
				preparedStmtDocument.execute();
				
				ResultSet resultSetDocument = (ResultSet) preparedStmtDocument.getGeneratedKeys(); 
				preparedStmtDocument.close();
				if ( resultSetDocument != null && resultSetDocument.next() ) 
				{ 
					// R�cup�ration de l'id du document ins�r�
					int documentId = resultSetDocument.getInt(1); 
					
					// ***************************************
					// Insertion des balises
					for (Balise balise : document.getBalises()) {
						if (bFirstBalise) {
							// Ajout de l'�l�ment en bdd
							queryInsertBalise.append("(" + baliseId + ", '"+balise.getName()+"', '"+balise.getPath()+"', "+documentId+")");
							bFirstBalise = false;
						} else {
							// Ajout de l'�l�ment en bdd
							queryInsertBalise.append(", (" + baliseId + ", '"+balise.getName()+"', '"+balise.getPath()+"', "+documentId+")");
							
						}
						// *********************************
						// Insertion des termes
						for (Terme terme : balise.getTermes()) {
							if (terme.getNom() != null) {
								// On r�cuere l'id du terme via la HashMap construite pr�c�dement
								Integer termeId = listTermsInserted.get(terme.getNom());
								if (termeId != null) {
									termeBaliseInsert++;
									if (bFirstTermeBalise) {
										// Ajout de l'�l�ment en bdd
										queryInsertTermeBalise.append("("+baliseId+", "+termeId+", '"+terme.getPositions()+"', "+terme.getNbOccurences()+")");
										bFirstTermeBalise = false;
									} else {
										// Ajout de l'�l�ment en bdd
										queryInsertTermeBalise.append(", ("+baliseId+", "+termeId+", '"+terme.getPositions()+"', "+terme.getNbOccurences()+")");
									}
								}
							} 
							// Si on a atteint la limite, on execute la requete
							if (CONSTANTS.TERME_BALISE_INSERT_LIMIT <= termeBaliseInsert){
								System.out.println(" -- Debut insertion \"balise\" et \"terme_balise\" -- ");
								queryInsertBalise.append(";");
								try {
									PreparedStatement preparedStmtBalise = con.prepareStatement(queryInsertBalise.toString());
									preparedStmtBalise.execute();
									preparedStmtBalise.close();
								} catch (SQLException e) {
									e.printStackTrace();
								}
								queryInsertBalise = new StringBuilder("INSERT INTO balise (id, nom, path, document_id) VALUES ");
								bFirstBalise = true;
								
								// On termine la construction de la requete d'insertion des terme_balises
								// Puis on l'execute
								queryInsertTermeBalise.append(";");

								try {
									PreparedStatement preparedStmtTermeBalise = con.prepareStatement(queryInsertTermeBalise.toString());
									preparedStmtTermeBalise.execute();
									preparedStmtTermeBalise.close();
								} catch (SQLException e) {
									e.printStackTrace();
								}
								
								// On recr� la requete
								queryInsertTermeBalise = new StringBuilder("INSERT INTO terme_balise (balise_id, terme_id, liste_position, frequence) VALUES ");
								bFirstTermeBalise = true;
								termeBaliseInsert = 0;
								System.out.println(" -- Fin insertion \"terme_balise\" -- ");
							}
							
						}
						baliseId++; // Incr�mentation de l'id de la balise
					}
				} 
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
		}
		// On termine la construction de la requete d'insertion des balises
		// Puis on l'execute
		System.out.println(" -- Insertion des �l�ments des tables \"balise\" et \"terme_balise\" en cours ... -- ");
		queryInsertBalise.append(";");
		try {
			PreparedStatement preparedStmtBalise = con.prepareStatement(queryInsertBalise.toString());
			preparedStmtBalise.execute();
			preparedStmtBalise.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		// On termine la construction de la requete d'insertion des terme_balises
		// Puis on l'execute
		queryInsertTermeBalise.append(";");

		try {
			PreparedStatement preparedStmtTermeBalise = con.prepareStatement(queryInsertTermeBalise.toString());
			preparedStmtTermeBalise.execute();
			preparedStmtTermeBalise.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Fonction permettant d'ins�rer tous les termes dans la base de donn�es
	 */
	public static void insertAllTerms() {
		// On insert tous les termes
		String queryInsertTerme = " INSERT INTO terme (nom) VALUES ";
		boolean bFirst = true;
		for (String terme : ParserHelper.getTermsList()) {
			String termeNormalize = Normalizer.normalize(terme, Normalizer.Form.NFD);
			termeNormalize = termeNormalize.replaceAll("[^\\p{ASCII}]", "");
			if (bFirst) {
				// Ajout de l'�l�ment en bdd
				queryInsertTerme += "('" + termeNormalize + "')";
				bFirst = false;
			} else {
				// Ajout de l'�l�ment en bdd
				queryInsertTerme += ", ('" + termeNormalize + "')";
			}
		}
		queryInsertTerme += ";";
		
		try {
			PreparedStatement preparedStmtTermes = getConnection().prepareStatement(queryInsertTerme);
			preparedStmtTermes.execute();
			preparedStmtTermes.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		 
		System.out.println(" -- Tous les termes (" + ParserHelper.getTermsList().size() + ") ont �t� ins�r�s en base ! --");
	}
	
	/**
	 * Fonction permettant de r�cuperer l'ensemble des �lements se trouvant dans la table "terme"
	 * Dans le but de construire une HashMap<Nom, Id>
	 * Ainsi, il est plus facile d'aceder � l'id d'un terme.
	 * C'est plus performant d'acceder � la HashMap plut�t que d'effectuer une requete � chaque fois
	 */
	public static void selectAllTerms() {
		// On r�cupere tous les termes de la base pour les mettre dans la HashMap<Integer, String>
		String query = "SELECT id, nom FROM terme;";
		
		// create the java statement
      	Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(query);
			
			while (rs.next()) {
				listTermsInserted.put(rs.getString("nom"), rs.getInt("id"));
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		System.out.println(" -- Fin de la r�cup�ration des termes -- ");
	}
	
	/**
	 * Fonction permettant de retourner la connection � la base de donn�es
	 * Dans le cas o� elle � d�ja �t� instanci�e, elle n'est pas recr�e de nouveau.
	 * On se sert directement de l'instance existante
	 * @return Connection
	 */
	public static Connection getConnection() 
	{
		if(connection == null)
		{
			MysqlDataSource source = new MysqlDataSource();
			source.setURL(CONSTANTS.MYSQL_URL);
			source.setUser(CONSTANTS.MYSQL_LOGIN);
			source.setPassword(CONSTANTS.MYSQL_PASSWD);
			try 
			{
				Connection connection = source.getConnection();
				return connection;
			}
			catch (SQLException e) 
			{
				e.printStackTrace();
			}
		}
		return connection;
	}
	
	public static void addDocument() {
		
	}
	
	/**
	 * Fonction permettant d'indiquer si la table est vide ou non
	 * @param tableName Nom de la table 
	 * @return boolean indiquant si la table est vide ou non
	 */
	public static boolean isTableEmpty(String tableName) {
		
		boolean bIsTableEmpty = true;
		
		String query = "SELECT * FROM " + tableName + " LIMIT 1 ";
		
		// create the java statement
      	Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			
			ResultSet rs = (ResultSet) st.executeQuery(query);

			if (rs.first()) {
				bIsTableEmpty = false;
		    }
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		return bIsTableEmpty;
	}
	
	
	
	
	
	/**
	 * Méthode permettant d'initialiser l'ensemble des tables de la base de données
	 */
	public static void initialiseBBd() {
		truncateTable("terme_balise");
		truncateTable("terme");
		truncateTable("balise");
		truncateTable("document");
		
		String queryMaxAllow = "SET GLOBAL max_allowed_packet = 20777216;";
		PreparedStatement preparedStmtMaxAllow;
		try {
			preparedStmtMaxAllow = getConnection().prepareStatement(queryMaxAllow);
			preparedStmtMaxAllow.execute();
			preparedStmtMaxAllow.close();
		} catch (SQLException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		System.out.println(" -- Initialisation de la base effectu�e ! -- ");
	}
	
	/**
	 * Méthode permettant de vider une table de la base de données
	 * @param tableName Nom de la table � vider
	 */
	public static void truncateTable(String tableName) {
		String queryDelete = "DELETE FROM " + tableName;
		try {
			PreparedStatement preparedStmt = getConnection().prepareStatement(queryDelete);
			preparedStmt.execute();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
	}
	
	/**
	 * Fonction peremttant de retourner le nombre d'élément contenu dans une table
	 */
	public static int getNbElementTable(String tableName, String id) {
		int nbElements = 0;
		String query = "SELECT COUNT(" + id + ") AS nb_elements FROM " + tableName;
		Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(query);
			
			while (rs.next()) 
			{
				nbElements = rs.getInt("nb_elements");
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		return nbElements;
	}
	
	/**
	 * Fonction permettant de retourner le nombre de balise pour lesquelles un terme apparait
	 * @return
	 */
	public static HashMap<Long, Integer> getDatabaseIEF()
	{
		HashMap<Long, Integer> IEF = new HashMap<Long, Integer>();
		String query = "SELECT terme.id, COUNT( DISTINCT ( balise.id ) ) AS nb_balises "
				+ "FROM terme "
				+ "INNER JOIN terme_balise ON terme.id = terme_balise.terme_id "
				+ "INNER JOIN balise ON terme_balise.balise_id = balise.id "
				+ "GROUP BY terme.id";
		
		Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(query);
			
			while (rs.next()) 
			{
				// TODO Calcul 
				IEF.put(rs.getLong("id"), rs.getInt("nb_balises"));
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		
		
		return IEF;
	}
	
	public static HashMap<Long, Integer> getNbElementByBalise(Long terme)
	{
		HashMap<Long, Integer> termsFreq = new HashMap<Long, Integer>();
		String query = "SELECT balise.id, COUNT( terme_id ) AS nb_elements"
			+ " FROM `balise` "
			+ " INNER JOIN terme_balise ON balise.id = terme_balise.balise_id ";
			if (terme != null) {
				query += " WHERE terme_balise.terme_id = '" + terme + "'";
			}
			query += " GROUP BY balise.id";
		
		//System.out.println(query);
			
		Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(query);
			
			while (rs.next()) 
			{
				// TODO Calcul 
				termsFreq.put(rs.getLong("id"), rs.getInt("nb_elements"));
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		
		
		return termsFreq;
	}
	
	/**
	 * Fonction retournant l'ensemble des fréquences de tous les termes_balise
	 * @return
	 */
	public static HashMap<Long, HashMap<Long, Integer>> getFrequences()
	{
		HashMap<Long, HashMap<Long, Integer>> termsFreq = new HashMap<Long, HashMap<Long, Integer>>();
//		HashMap<Long, Integer> termsFreq = new HashMap<Long, Integer>();
		String query = "SELECT terme_balise.terme_id, terme_balise.balise_id, frequence AS frequence"
			+ " FROM `terme_balise` ORDER BY terme_id, balise_id";
		
		Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(query);
			long terme_id = 0;
			HashMap<Long, Integer> listeBaliseFrequence = new HashMap<Long, Integer>();
			while (rs.next()) 
			{
				if (terme_id != rs.getLong("terme_id")) {
					termsFreq.put(terme_id, listeBaliseFrequence);
					listeBaliseFrequence = new HashMap<Long, Integer>();
					terme_id = rs.getLong("terme_id");
				}
				listeBaliseFrequence.put(rs.getLong("balise_id"), rs.getInt("frequence"));
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		return termsFreq;
	}
	
	/**
	 * Fonction permettant d'ins�rer tous les termes dans la base de donn�es
	 */
	public static void updatePoids(HashMap<Long, HashMap<Long, Float>> listPoids) {
		
		
		int compteur = 0;
		int limite = 10000;
		Connection con = getConnection();
		PreparedStatement pst = null;
		try {
			pst = con.prepareStatement("");
			con.setAutoCommit(false);
		} catch (SQLException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		for (Long terme_id : listPoids.keySet()) {
			for (Long balise_id : listPoids.get(terme_id).keySet()) {
				try {
					pst.addBatch("UPDATE terme_balise SET poids = '" + listPoids.get(terme_id).get(balise_id) + "'"
							+ " WHERE terme_balise.terme_id = '" + terme_id +  "'"
							+ "	AND terme_balise.balise_id = '" + balise_id +  "';");
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
				compteur++;
				if (compteur % (limite) == 0){
					try {
						pst.executeBatch();
						con.commit();
						System.out.println("-- " + compteur + " updated ! --");
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
			}
		}
		try {
			pst.executeBatch();
			con.commit();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 
		System.out.println(" -- Poids inséré --");
	}
	/**
	 * 
	 * @param elements
	 */
	public static HashMap<Integer, Balise> getItemsWithQuery(Query q)
	{
		if(q.getWordsToSeek() == null || q.getWordsToSeek().isEmpty())
		{
			return null;
		}

		HashMap<Integer, Balise> listTermsForSearch = new HashMap<>();
		
		StringBuilder request = new StringBuilder();
		request.append("SELECT terme_id, balise_id, terme.nom AS terme_nom, liste_position, path, poids, document.nom AS doc_nom "
				+ "FROM terme "
				+ "INNER JOIN terme_balise ON terme_balise.terme_id = terme.id "
				+ "INNER JOIN balise ON balise.id = terme_balise.balise_id "
				+ "INNER JOIN document ON document.id = balise.document_id"
				+ " WHERE "
				+ "terme.id = terme_id AND "
				+ "balise.id = balise_id "
				+ " AND terme.nom IN (");
		int cmp = 0;
		for(String str : q.getWordsToSeek()) {
			if (cmp == 0) {
				request.append("'" + str + "'");
			} else {
				request.append(", '" + str + "'");
			}
			cmp++;
		}
		
		for(String str : q.getSynonymsToSeek()) 
		{
			if (cmp == 0) {
				request.append("'" + str + "'");
			} else {
				request.append(", '" + str + "'");
			}
			cmp++;
		}
		
		for(String str : q.getParentsToSeek()) 
		{
			if (cmp == 0) {
				request.append("'" + str + "'");
			} else {
				request.append(", '" + str + "'");
			}
			cmp++;
		}
		
		for(String str : q.getSubClassToSeek()) 
		{
			if (cmp == 0) {
				request.append("'" + str + "'");
			} else {
				request.append(", '" + str + "'");
			}
			cmp++;
		}
		
		request.append(");");
		
		
		// create the java statement
      	Statement st;
		try {
			st = (Statement) getConnection().createStatement();
			ResultSet rs = (ResultSet) st.executeQuery(request.toString());
			
			while (rs.next()) 
			{
				if(!listTermsForSearch.containsKey(rs.getInt("balise_id")))
				{
					Balise b = new Balise();
					b.setName("");
					b.setPath(rs.getString("path"));
					b.setDoc(rs.getString("doc_nom"));
					listTermsForSearch.put(rs.getInt("balise_id"), b);
				}
				listTermsForSearch.get(rs.getInt("balise_id")).addTerms(rs.getString("terme_nom"), rs.getString("liste_position"), rs.getDouble("poids"));
			}
			st.close();
	    } catch (SQLException e) {
			e.printStackTrace();
		} 
		System.out.println(" -- Fin de la r�cup�ration des termes -- ");
		return listTermsForSearch;
	}
}
